<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2019/3/19
 * Time: 23:16
 */
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
 * xss安全过滤
 * */
function safe_html($html)
{
    $elements = [
        'html' => [],
        'body' => [],
        'a' => ['target', 'href', 'title', 'class', 'style'],
        'abbr' => ['title', 'class', 'style'],
        'address' => ['class', 'style'],
        'area' => ['shape', 'coords', 'href', 'alt'],
        'article' => [],
        'aside' => [],
        'audio' => ['autoplay', 'controls', 'loop', 'preload', 'src', 'class', 'style'],
        'b' => ['class', 'style'],
        'bdi' => ['dir'],
        'bdo' => ['dir'],
        'big' => [],
        'blockquote' => ['cite', 'class', 'style'],
        'br' => [],
        'caption' => ['class', 'style'],
        'center' => [],
        'cite' => [],
        'code' => ['class', 'style'],
        'col' => ['align', 'valign', 'span', 'width', 'class', 'style'],
        'colgroup' => ['align', 'valign', 'span', 'width', 'class', 'style'],
        'dd' => ['class', 'style'],
        'del' => ['datetime'],
        'details' => ['open'],
        'div' => ['class', 'style'],
        'dl' => ['class', 'style'],
        'dt' => ['class', 'style'],
        'em' => ['class', 'style'],
        'font' => ['color', 'size', 'face'],
        'footer' => [],
        'h1' => ['class', 'style'],
        'h2' => ['class', 'style'],
        'h3' => ['class', 'style'],
        'h4' => ['class', 'style'],
        'h5' => ['class', 'style'],
        'h6' => ['class', 'style'],
        'header' => [],
        'hr' => [],
        'i' => ['class', 'style'],
        'img' => ['src', 'alt', 'title', 'width', 'height', 'id', 'class'],
        'ins' => ['datetime'],
        'li' => ['class', 'style'],
        'mark' => [],
        'nav' => [],
        'ol' => ['class', 'style'],
        'p' => ['class', 'style'],
        'pre' => ['class', 'style'],
        's' => [],
        'section' => [],
        'small' => [],
        'span' => ['class', 'style'],
        'sub' => ['class', 'style'],
        'sup' => ['class', 'style'],
        'strong' => ['class', 'style'],
        'table' => ['width', 'border', 'align', 'valign', 'class', 'style'],
        'tbody' => ['align', 'valign', 'class', 'style'],
        'td' => ['width', 'rowspan', 'colspan', 'align', 'valign', 'class', 'style'],
        'tfoot' => ['align', 'valign', 'class', 'style'],
        'th' => ['width', 'rowspan', 'colspan', 'align', 'valign', 'class', 'style'],
        'thead' => ['align', 'valign', 'class', 'style'],
        'tr' => ['rowspan', 'align', 'valign', 'class', 'style'],
        'tt' => [],
        'u' => [],
        'ul' => ['class', 'style'],
        'video' => ['autoplay', 'controls', 'loop', 'preload', 'src', 'height', 'width', 'class', 'style'],
        'embed' => ['src', 'height', 'align', 'width', 'class', 'style', 'type', 'pluginspage', 'wmode', 'play', 'loop', 'menu', 'allowscriptaccess', 'allowfullscreen'],
        'source' => ['src', 'type']
    ];
    $html = strip_tags($html, '<' . implode('><', array_keys($elements)) . '>');
    $xml = new \DOMDocument();
    libxml_use_internal_errors(true);
    if (!strlen($html)) {
        return '';
    }
    if ($xml->loadHTML('<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . $html)) {
        foreach ($xml->getElementsByTagName("*") as $element) {
            if (!isset($elements[$element->tagName])) {
                $element->parentNode->removeChild($element);
            } else {
                for ($k = $element->attributes->length - 1; $k >= 0; --$k) {
                    if (!in_array($element->attributes->item($k)->nodeName, $elements[$element->tagName])) {
                        $element->removeAttributeNode($element->attributes->item($k));
                    } elseif (in_array($element->attributes->item($k)->nodeName, ['href', 'src', 'style', 'background', 'size'])) {
                        $_keywords = ['javascript:', 'javascript.:', 'vbscript:', 'vbscript.:', ':expression'];
                        $find = false;
                        foreach ($_keywords as $a => $b) {
                            if (false !== strpos(strtolower($element->attributes->item($k)->nodeValue), $b)) {
                                $find = true;
                            }
                        }
                        if ($find) {
                            $element->removeAttributeNode($element->attributes->item($k));
                        }
                    }
                }
            }
        }
    }
    $html = substr($xml->saveHTML($xml->documentElement), 12, -14);
    $html = strip_tags($html, '<' . implode('><', array_keys($elements)) . '>');
    return $html;
}


//输出安全的html
function aq_html($text, $tags = null)
{
    $text = trim($text);
    $text = preg_replace('/<!--?.*-->/', '', $text);//完全过滤注释
    $text = preg_replace('/<\?|\?' . '>/', '', $text);//完全过滤动态代码
    $text = preg_replace('/<script?.*\/script>/', '', $text);//完全过滤js
    $text = str_replace('[', '&#091;', $text);
    $text = str_replace(']', '&#093;', $text);
    $text = str_replace('|', '&#124;', $text);
    $text = preg_replace('/\r?\n/', '', $text);//过滤换行符
    $text = preg_replace('/<br(\s\/)?' . '>/i', '[br]', $text);//<br> 转 [br]
    $text = preg_replace('/(\[br\]\s*){10,}/i', '[br]', $text);
    //过滤危险的属性，如：过滤on事件lang js
    while (preg_match('/(<[^><]+)( lang|on|action|background|codebase|dynsrc|lowsrc)[^><]+/i', $text, $mat)) {
        $text = str_replace($mat[0], $mat[1], $text);
    }
    while (preg_match('/(<[^><]+)(window\.|javascript:|js:|about:|file:|document\.|vbs:|cookie)([^><]*)/i', $text, $mat)) {
        $text = str_replace($mat[0], $mat[1] . $mat[3], $text);
    }
    if (empty($tags)) {
        $tags = 'table|td|th|tr|i|b|u|strong|img|p|br|div|strong|em|ul|ol|li|dl|dd|dt|a';
    }
    $text = preg_replace('/<(' . $tags . ')( [^><\[\]]*)>/i', '[\1\2]', $text);//允许的HTML标签
    //过滤多余html
    $text = preg_replace('/<\/?(html|head|meta|link|base|basefont|body|bgsound|title|style|script|form|iframe|frame|frameset|applet|id|ilayer|layer|name|script|style|xml)[^><]*>/i', '', $text);
    //过滤合法的html标签
    while (preg_match('/<([a-z]+)[^><\[\]]*>[^><]*<\/\1>/i', $text, $mat)) {
        $text = str_replace($mat[0], str_replace('>', ']', str_replace('<', '[', $mat[0])), $text);
    }
    //转换引号
    while (preg_match('/(\[[^\[\]]*=\s*)(\"|\')([^\2=\[\]]+)\2([^\[\]]*\])/i', $text, $mat)) {
        $text = str_replace($mat[0], $mat[1] . '|' . $mat[3] . '|' . $mat[4], $text);
    }
    //过滤错误的单个引号
    while (preg_match('/\[[^\[\]]*(\"|\')[^\[\]]*\]/i', $text, $mat)) {
        $text = str_replace($mat[0], str_replace($mat[1], '', $mat[0]), $text);
    }
    //转换其它所有不合法的 < >
    $text = str_replace('<', '&lt;', $text);
    $text = str_replace('>', '&gt;', $text);
    $text = str_replace('"', '&quot;', $text);
    //反转换
    $text = str_replace('[', '<', $text);
    $text = str_replace(']', '>', $text);
    $text = str_replace('|', '"', $text);
    //过滤多余空格
    $text = str_replace('  ', ' ', $text);
    return $text;
}